# -*- coding:utf-8 -*-
## author : cypro666
## note   : python3.4+
"""
simple wrapper of pymongo
"""
import sys
from copy import deepcopy
from urllib.parse import quote
import pymongo
from pymongo import MongoClient, ReadPreference
from bson.code import Code

from .debug import time_meter, print_exception
from .utilities import loadjson

__all__ = ['OpMongo', 'MongoErrors']

# default mongodb host and port
MONGO_ADDR = ('localhost', 27017)
MONGO_HOST, MONGO_PORT = MONGO_ADDR

MongoErrors = pymongo.errors.PyMongoError


class OpMongo(object):
    """ wrapper of mongodb operations """
    def __init__(self, **kwargs):
        """ `kwargs` is a dict(always from json) like: 
            {
                "host"     : "192.168.1.33",
                "post"     : 27017,
                "username" : "root"
                "password" : "bingo321"
                "dbname"   : "spider"
            }
        """
        super(OpMongo, self).__init__()
        host = kwargs['host']
        port = kwargs['port']
        username = kwargs['username']
        password = kwargs['password']
        dbname = kwargs['dbname']
        if not password:
            con_uri = host
        else:
            con_uri = 'mongodb://%s:%s@%s:%d/%s' % (username,quote(password),host,port,dbname)
        try:
            sys.stdout.write(con_uri+'\n')
            self.__mc = MongoClient(con_uri)
            self.__db = self.__mc[dbname]
            self.__coll = None
        except Exception as e:
            print_exception('OpMongo.__init__')
            raise e

    def __del__(self):
        """ close on exit """
        self.__mc.close()
        
    def lists(self):
        """ return collection names current """
        assert self.__db
        return deepcopy(self.__db.collection_names())
    
    def choose(self, collname):
        """ select another collection """
        assert isinstance(collname, str)
        self.__coll = self.__db[str(collname)]
    
    def info(self):
        """ get infos """
        s = 'connected to: %s\nchosen collection: %s\n'
        if self.__coll.count(): 
            s += 'Warning : %s is Not Empty !!!' % self.__coll.full_name
        return s % (str(self.__db), self.__coll.full_name)
    
    def remove(self):
        """ remove collection, this is dangerous!!! """
        if self.__coll.count():
            sure = eval(input("Are you sure remove old collection? yes/no\n"))
            if sure.upper() == 'YES' or sure.upper() == 'Y':
                self.__remove__()
                sys.stderr.write('%s removed!\n' % self.__coll.full_name) 
    
    def __remove__(self):
        self.__coll.remove()
    
    
    def execute(self, javascript, *args):
        """ execute `javascript` with optional `args`, low level """
        result = self.__db.command('$eval', 
                                    Code(javascript),
                                    read_preference = ReadPreference.PRIMARY,
                                    args = args)
        return result.get('retval', None)
    
    
    def update(self, spec, doc, upsert = False):
        """ update or insert(upsert) specified by `spec` with doc """
        try:
            self.__coll.update(spec, doc, upsert = upsert)
        except Exception:
            print_exception('OpMongo.update')
    
    
    #@time_meter('OpMongo.insert')
    def insert(self, docs, fsync = False):
        """ insert doc, if fsync is True, will be very very slow! """
        try:
            objects_id = self.__coll.insert(docs, fsync)
            return objects_id
        except Exception:
            print_exception('OpMongo.insert')
            return None
    
    
    def count(self):
        """ count of current collection """
        return self.__coll.count()
    
    
    def create_indexes(self, indexlist):
        """ create indexes(means not only one) on current collection if enable """ 
        try:
            if indexlist:
                index = list(map(self.__coll.create_index, indexlist))
                sys.stderr.write('indexes : %s\n' % str(index))
        except Exception:
            print_exception('OpMongo.create_index')
    
    
    def find_one(self, query):
        """ query and return only one doc if found """
        try:
            cursor = self.__coll.find_one(query)
            return cursor
        except Exception:
            print_exception('OpMongo.find_one')
            return None
    
    
    def explain(self, cursor):
        """ explain """
        s = str(cursor.explain()).replace('u\'', '\'')
        s = s.lstrip('{').rstrip('}').replace(',', ',\n    ')
        sys.stderr.write('-------------------- Mongo Explain --------------------\n')
        sys.stderr.write('{\n    %s\n }\n' % s)
        sys.stderr.write('-------------------------------------------------------\n')
    
    
    def find(self, query = {}, sortkey = None, reverse = True, explain = False):
        """ find doc specified by `query`, if `sortkey`, sort result """
        try:
            cursor = self.__coll.find(query)
            if explain:
                self.explain(cursor)
            if sortkey:
                cursor.sort(sortkey, pymongo.DESCENDING if reverse else pymongo.ASCENDING)
            return cursor
        except Exception:
            print_exception('OpMongo.find')
            return None



@time_meter(__name__)
def test(jsonfile):
    ''' Simple tester for opmongo '''
    cfg = loadjson(jsonfile)
    db = OpMongo(**cfg)
    colls = db.lists()
    print(colls)
    db.choose(colls[0])
    print(list(db.find({}, explain=True)))

    


